'use strict';
function bake(size, render) {
	let img = new Image(size.x, size.y);
	let cvs = document.createElement('canvas');
	cvs.width = size.x; cvs.height = size.y;
	
	render(cvs.getContext('2d'));
	img.src = cvs.toDataURL('image/png');
	cvs.remove();
	return img;
};




class Value extends EventEmitter {
	constructor(value = null, auto_updata) {
		super();
		for(let i of ['_type', '_value', '_setter', '_getter', '_oldListenersClear']) setPropertyNotEnumerable(this, i, null);
		setPropertyNotEnumerable(this, 'auto_updata', auto_updata||false);
		setPropertyNotEnumerable(this, 'locked', false);
		Value.init.call(this, value);
	}
	set value(v) {
		let prev = this._value;
		let value = this._setter?.(v);
		this.emit('assign', this._value, prev, value);
	}
	get value() { return this._getter(); }
	get type() { return this._type; }
	
	set new(v) {
		this._oldListenersClear?.();
		let prev = this._value;
		Value.init.call(this, v);
		this.emit('new', this._value, prev);
	}
	get new() { return this.value; }
	
	lock(a) { this.locked = a !== undefined ? a : !this.locked; }
	
	valueOf() { return this.value; }
	toString() { return this.value; }
	[Symbol.toPrimitive](type) { return this.value; }
	
	static init(value) {
		let self = this;
		let t = typeof value;
		
		if(value === null || t !== 'object' && t !== 'function') {
			this._type = 'primitive';
			if(value === null) this._type = 'null';
			this._value = value;
			
			this._getter = () => this._value;
			this._setter = v => this._value = v;
		} else if(value instanceof Value) {
			this._type = 'ref'; // value reference
			this._getter = () => value.value;
			this._setter = value.locked ? null : value._setter;
			
			// TODO: убрать потписку на событие "new"
			let assignListener = (...args) => this.emit('assign', ...args);
			let newListener = (...args) => this.emit('new', ...args);
			let evaluateListener = (...args) => this.emit('evaluate', ...args);
			
			value.on('assign', assignListener).on('new', newListener).on('evaluate', evaluateListener);
			this._oldListenersClear = () => value.off('assign', assignListener).off('new', newListener).off('evaluate', evaluateListener);
		} else if(t === 'function') {
			this._type = 'expression'; // value expression
			let api = {
				get value() { return self._value; },
				set value(v) { return self._value = v; },
				
				get evaluate() { return self.evaluate; },
				
				get auto_updata() { return self.auto_updata; },
				set auto_updata(v) { return self.auto_updata = v; }
			};
			setPropertyNotEnumerable(this, 'evaluate', (...args) => {
				let prev = this._value;
				let v = value.apply(api, args);
				this.emit('evaluate', this._value, prev, args);
				return v;
			});
			if(!this.auto_updata) this.evaluate();
			this._getter = Value.getter_expression;
		};
	}
	static getter_expression() {
		if(this.auto_updata) this.evaluate();
		return this._value;
	}
};
setPropertyNotEnumerable(Value.prototype, Symbol.toStringTag, 'Value');


/*
let hendler = function(value, prev) {
	console.log(`${this.type}: ${value}, ${prev}`);
};


let aa = new Value('aa: '+28);
let bb = new Value('bb: '+30);

let cc = new Value(aa);

cc.on('new', hendler);
cc.on('assign', hendler);

console.log(aa.value, bb.value, cc.value);
cc.new = 'frkdk';
cc.value = 'frkdk';
console.log(aa.value, bb.value, cc.value);



//*
let exp = function() {
	let r = Math.random();
	
	console.group('exp');
	console.log(uu);
	
	this.value = 'id: '+BigInt(Math.floor(r*1e20));
};
exp.api = {
	uu: 2822338,
	
	Math, console,
	Object, Array, Function,
	String, Number, BigInt, Boolean, Symbol
};


let ff = new Value(codeFunction(exp, exp.api, 'exp'), true);
ff.on('evaluate', hendler);

console.log(ff.value);
console.groupEnd();
console.log(ff.value);
console.groupEnd();

let vRef = new Value(vExp);

vExp.on('evaluate', value => vRef.evaluate());

vExp.evaluate(444);
//*/